package edu.upenn.seas.peerreview.smui.variants

import edu.upenn.seas.peerreview.{Id, Sink}
import scala.util.Random

/**
 * A bot player that makes fairly intelligent logic decisions. Displays
 * "non-deterministic" behavior, actually seeded by random number class argument.
 */
class DecentBotDecider(private val rand : Random) extends Decider {
  private var getBoard : Id => Board = null
  private var boardSize : Point = null
  
  // Constructor variant with time-seeded random.
  def this() = this(new Random())
    
  override def onRegister(getMyBoard: () => Board, getOpponentBoard: Id => Board) {
    // Ignore unused functions.
    getBoard = getOpponentBoard
  }
  
  override def placeShips(bSize:Point, shipSizes:List[Int]) : List[(Point,Point)] = {
    boardSize = bSize
    var ocean : Set[Point] = Set()
    
    def isUnoccupied(p : Point) = p.x >= 0 && p.x < boardSize.x &&
        p.y >= 0 && p.y < boardSize.y && !ocean.contains(p)
   
    def getPlacement(size : Int, depth : Int) : (Point, Point) = {
      if (depth > 10000) {
        // Fail-safe: In the unlikely case that an earlier placement
        // blocks all future placement options, we want to terminate instead
        // of running an infinite loop.
        // TODO: Instead of throwing ex, instead replace earlier ships.
        throw new RuntimeException("Ship placement failed unexpectedly.")
      }
      val loc = (rand.nextInt(boardSize.x), rand.nextInt(boardSize.y))
      val dir = "se".charAt(rand.nextInt(2)) 
      val add = dir match {
        case 's' => (0, 1)
        case 'e' => (1, 0)
        case  _  => throw new RuntimeException("Bad direction.")
      }
      var points = (for (i <- 0 to size-1) 
          yield Point(loc._1 + i * add._1, loc._2 + i * add._2)).toList
      if (points.forall(p => isUnoccupied(p))) { 
        points.foreach(ocean += _); (points.head, points.last)
      } else getPlacement(size, depth+1)
    }      
    
    val result = shipSizes.map(getPlacement(_,0))
    result
  }
  
  override def makeMove(opps : List[Id]) : (Id, Point) = {
    val opp = opps.sort(_.toString<_.toString)(rand.nextInt(opps.size))
    //for all 'hits' - try to shoot around.
    val oppBoard = getBoard(opp)
    val allPointsStatusMap = Map() ++ (for(w <- 0 to oppBoard.width; 
                        h <- 0 to oppBoard.height)
                          yield (Point(w,h) -> oppBoard.examine(Point(w,h))))
    val hitPoints = allPointsStatusMap.filter(
      kvp => kvp._2 == CellStatus.Hit).map(_._1)
    
    //has redundancies; this is OK
    val neighborLists = hitPoints.map(neighbors(_))
    
    val emptyTargetsNearHPs = 
      if(!neighborLists.isEmpty) 
        neighborLists.reduceLeft(_++_).filter(
        allPointsStatusMap(_) == CellStatus.Empty)
      else
        List()
    if(!emptyTargetsNearHPs.isEmpty) 
      (opp, randPoint(emptyTargetsNearHPs))
    else {
    //if none are available, 
    //shoot randomly at somewhere that hasn't been shot at yet
      val allTargets = allPointsStatusMap.filter(_._2 == CellStatus.Empty).map(_._1)
      if(!allTargets.isEmpty) (opp, randPoint(allTargets))
      else (opp, Point(0,0))//fine, just fire somewhere
    }
  }      
  
  private[this] def neighbors(p : Point) : List[Point] = 
    ((if(p.x != 0) Point(p.x-1, p.y) else null) ::
    (if(p.y != 0) Point(p.x, p.y-1) else null) ::
    (if(p.x != boardSize.x - 1) Point(p.x+1, p.y) else null) ::
    (if(p.y != boardSize.y - 1) Point(p.x, p.y+1) else null) 
    :: List[Point]()).filter(_ != null).toList
  
  private[this] def randPoint(points:Iterable[Point]) : Point =
    //sorted to ensure determinism (points coming from map)
    points.toList.sort(_<_)(rand.nextInt(points.toList.size))
}
